/*
 * $Id: MPGraphics2D.java,v 1.2 2003/05/31 07:50:37 milca Exp $
 *
 * Copyright (c) Milan Jedlicka
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

package net.sf.vofce.metapost;

import java.text.AttributedCharacterIterator;
import java.awt.*;
import java.awt.image.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.renderable.*;

import java.io.*;

import org.apache.batik.ext.awt.g2d.*;

import org.apache.log4j.Category;

/**
 * This implementationm of the java.awt.Graphics2D abstract class 
 * allows users to generate METAPOST content from Java code.
 *
 * @author Milan Jedlicka
 */
public class MPGraphics2D extends AbstractGraphics2D implements Cloneable {
	
	/** Logger instance. */
	protected static Category logger = Category.getRoot();
	
	/** Whole context with header and tail */
	public static final int WHOLE_CONTEXT = 0;

	/** Context without header and tail for inline insertion of METAPOST code */
	public static final int IN_LINE_CONTEXT = 1;
	
	private static final int FILL = 0;
	private static final int NO_FILL = 1;
	private static final int CLIP = 2; // Not used yet
	
	private Dimension dim; 
	
	// Generators

	/** METAPOST header and tail generator */
	private MPHTGenerator htGen;
	
	/** Generator of METAPOST syntax */
	private MPSyntaxGenerator synGen;

	
	// Output  

	/** Generate for standalone use or not. */
	private int context = WHOLE_CONTEXT;
		
	/** File where to generate code */
	protected File exportFile;

	/** Stream where to generate METAPOST code */
	protected PrintWriter stream;	
	// --
	
	private Graphics2D fmg;  // Used to create proper font metrics

	/** 
	 * This constructor supports the create method 
	 */
	public MPGraphics2D(MPGraphics2D g) {
		super(g);
	}

	/**
         * Creates a new <code>Graphics</code> object that is
         * a copy of this <code>Graphics</code> object.
	 * @return     a new graphics context that is a copy of
	 *             this graphics context.
	 */
	public Graphics create() {
		return new MPGraphics2D(this);
	}

	/**
	 * Default constructor 
	 */
	public MPGraphics2D(Dimension dim) {
		super(false);
	
		this.dim = dim;
		
		synGen = new MPSyntaxGenerator();
		this.gc = new MPGraphicContext(synGen);
	}


//----------------------------------------------------
//	    Export starting/finishing methods
//----------------------------------------------------
	
	/** 
	 * Starts exporting process to file.
	 * @param  exportFile the <code>File</code> where to export
	 * @param  context    type of context which to generate
	 */
	public void startExport(File exportFile, int context) {
		this.exportFile = exportFile;	
		this.context = context;

		try {
			FileWriter fw = new FileWriter(exportFile);
			stream = new PrintWriter(fw);
			((MPGraphicContext) (this.gc)).setStream(stream);
			initExport();
		} catch(IOException ioe) {
			logger.error("ERROR: FileWriter init failed!");
		}
	}

	/** 
	 * Starts exporting process to file.
	 * @param  fileName   the name of file where to export
	 * @param  context    type of context which to generate
	 */
	public void startExport(String fileName, int context) {
		File exportFile = new File(fileName);
		startExport(exportFile, context);
	}

	/** 
	 * Starts exporting process to writer.
	 * @param  writer     the <code>Writer</code> where to export
	 * @param  context    type of context which to generate
	 */
	public void startExport(Writer writer, int context) {
		this.context = context;
		stream = new PrintWriter(writer);
		((MPGraphicContext) (this.gc)).setStream(stream);
		initExport();
	}
	
	/**
	 * Initialization of METAPOST file.
	 * Before the generation of code can begin we have to set up some basic variables (such as transformations)
	 * and of course generate the header of METAPOST file (image) 
	 */
	public void initExport() {
		htGen = new MPHTGenerator(stream);
		htGen.setGeneratedByComment("Generated By MetaPost export package (c) Milan Jedlicka");                
		
		if (this.context == WHOLE_CONTEXT) { 
			htGen.doFileHeader(); 
			htGen.doPictHeader(-1);
		}

		stream.println( synGen.declaration("transform", "default") );
		stream.println( synGen.assignment("default", "identity" + " " 
					+ synGen.reflect(
						synGen.pair(0, this.dim.getHeight()/2), 
							synGen.pair(this.dim.getWidth(), this.dim.getHeight()/2))
					) 
				); 
	
		stream.println( synGen.declaration("transform", "t" ) );
		stream.println( synGen.assignment("t", "identity") );
	
		stream.println( synGen.declaration("transform", "pent" ) );
		stream.println( synGen.assignment("pent", "identity") );
		
		stream.println( synGen.declaration("picture", "picts[]" ) );

		stream.println( synGen.declaration("path", "clips[]") );
	}
	
	/**
	 * Finishes export to METAPOST. 
	 * Generates tail of current image. Then we close the stream where we generated to.
	 */
        public void endExport() {
		String clipedImages = ((MPGraphicContext) gc).getClipedImages();
		if (clipedImages != null) stream.println(clipedImages);

		if (this.context == WHOLE_CONTEXT) { 
			htGen.doPictTail();
			htGen.doFileTail();
		}
    		
		stream.close();
	}
	

//----------------------------------------------------
//		Objects converting methods
//----------------------------------------------------
	
	/**
     	 * Draws a line, using the current color, between the points
         * <code>(x1,&nbsp;y1)</code> and <code>(x2,&nbsp;y2)</code>
	 * in this graphics context's coordinate system.
	 * @param   x1  the first point's <i>x</i> coordinate.
      	 * @param   y1  the first point's <i>y</i> coordinate.
	 * @param   x2  the second point's <i>x</i> coordinate.
	 * @param   y2  the second point's <i>y</i> coordinate.
    	 */
	public void drawLine(int x1, int y1, int x2, int y2){
		stream.println( synGen.declaration( "path", "line" ) ); 
		stream.println( synGen.assignment("line", synGen.pair(x1, y1) + "--" + synGen.pair(x2, y2))  );
		stream.println( synGen.commandLine( "draw", "line" + " " + "transformed t" + " " 
						+ "transformed default" + " "
						+ ((MPGraphicContext) this.gc).drawOptionsDescriptor()) 
						);
	}

	/**
     	 * Draws a rectangle, using the current color
  	 * in this graphics context's coordinate system.
	 * @param   x  		the left right corner of the rectangle point's <i>x</i> coordinate.
	 * @param   y  		the left right corner of the rectangle point's <i>y</i> coordinate.
	 * @param   width	the width of the rectangle
	 * @param   height 	the height of the rectangle 
	 */
	public void drawRect(int x, int y, int width, int height){
		makeRect(x, y, width, height, NO_FILL);	
	}

	/**
     	 * Fills the specified rectangle.
	 * @param   x   	the <i>x</i> coordinate of the rectangle to be filled.
         * @param   y   	the <i>y</i> coordinate of the rectangle to be filled.
     	 * @param   width   	the width of the rectangle to be filled.
	 * @param   height   	the height of the rectangle to be filled.
	 */
	public void fillRect(int x, int y, int width, int height) {
		makeRect(x, y, width, height, FILL);
	}

	/** 
	 * Generates METAPOSt code for the rectangle witch have to be filled or drawn.
	 * @param   x  		the left right corner of the rectangle point's <i>x</i> coordinate.
	 * @param   y  		the left right corner of the rectangle point's <i>y</i> coordinate.
	 * @param   width	the width of the rectangle
	 * @param   height 	the height of the rectangle 
	 * @param   fill 	if TRUE then FILL else DRAW 
	 */
	public void makeRect(int x, int y, int width, int height, int fill) {
	
		String command = null;
		
		switch(fill) {
			case 0: command = "fill"; break;
			case 1: command = "draw"; break;
			case 2: command = "clip"; break;
		}
		
		stream.println( synGen.commandLine( command, "unitsquare" + " " 
					+ synGen.scale(width,  height) + " " 
					+ synGen.shift(synGen.pair(x, y))  + " "
					+ "transformed t" + " " + "transformed default" + " "
					+ ((MPGraphicContext) this.gc).drawOptionsDescriptor()) 
				);
	}

        /**
      	 * Draws the outline of an oval.
   	 * The result is a circle or ellipse that fits within the
	 * rectangle specified by the <code>x</code>, <code>y</code>,
	 * <code>width</code>, and <code>height</code> arguments.
	 * <p>
	 * The oval covers an area that is
	 * <code>width&nbsp;+&nbsp;1</code> pixels wide
	 * and <code>height&nbsp;+&nbsp;1</code> pixels tall.
	 * @param 	x the <i>x</i> coordinate of the upper left  
	 *                     corner of the oval to be drawn.
	 * @param       y the <i>y</i> coordinate of the upper left
         *                     corner of the oval to be drawn.
         * @param       width the width of the oval to be drawn.
         * @param       height the height of the oval to be drawn.
	 */
	public void drawOval(int x, int y, int width, int height) {
		makeOval(x, y, width, height, NO_FILL);
	}
	
	/**
         * Fills an oval bounded by the specified rectangle with the
         * current color.
	 * @param       x the <i>x</i> coordinate of the upper left corner
      	 *                     of the oval to be filled.
	 * @param       y the <i>y</i> coordinate of the upper left corner
	 *                     of the oval to be filled.
	 * @param       width the width of the oval to be filled.
	 * @param       height the height of the oval to be filled.
	 */			  
	public void fillOval(int x, int y, int width, int height) {
		makeOval(x, y, width, height, FILL);
	}

	/** 
	 * Generates METAPOST code for the oval witch have to be filled or drawn.
	 * @param   x  		the left right corner of the rectangle point's <i>x</i> coordinate.
	 * @param   y  		the left right corner of the rectangle point's <i>y</i> coordinate.
	 * @param   width	the width of the rectangle
	 * @param   height 	the height of the rectangle 
	 * @param   fill 	if TRUE then FILL else DRAW 
	 */
	public void makeOval(int x, int y, int width, int height, int fill) {
	
		String command = null;
		
		switch(fill) {
			case 0: command = "fill"; break;
			case 1: command = "draw"; break;
			case 2: command = "clip"; break;
		}
		
		stream.println( synGen.commandLine( command, "fullcircle" + " "  
					+ synGen.scale(width,  height) + " " 
					+ synGen.shift(synGen.pair(x + width/2, y + height/2)) + " " 
					+ "transformed t" + " " + "transformed default" + " "
					+ ((MPGraphicContext) this.gc).drawOptionsDescriptor()) 
					); 
	}

	/**
     	 * Draws a polyline, using the current color, between the points
         * <code>(x1,&nbsp;y1)</code> and <code>(x2,&nbsp;y2)</code> ... and <code>(x_n,&nbsp;y_n)</code>
	 * in this graphics context's coordinate system.
	 * @param   xPoints  the <i>x</i> coordinates of points.
      	 * @param   yPoints  the <i>y</i> coordinates of points.
	 * @param   nPoints  number of points.
    	 */
	public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
		if(nPoints > 0) {
			String path ="";
			path += synGen.pair(xPoints[0], yPoints[0]);
			for (int i=1; i < nPoints; i++) {
				path += "--" + synGen.pair(xPoints[i], yPoints[i]);
			}

			stream.println( synGen.declaration( "path", "polyline" ) ); 
			stream.println( synGen.assignment( "polyline", path ) ); 
			stream.println( synGen.commandLine("draw", "polyline" + " "
						+ "transformed t" + " " + "transformed default" + " "
						+ ((MPGraphicContext) this.gc).drawOptionsDescriptor())
						);
		}
	}

	/**
     	 * Draws a polygon, using the current color, bounded by points
         * <code>(x1,&nbsp;y1)</code> and <code>(x2,&nbsp;y2)</code> ... and <code>(x_n,&nbsp;y_n)</code>
	 * in this graphics context's coordinate system.
	 * @param   xPoints  the <i>x</i> coordinates of points.
      	 * @param   yPoints  the <i>y</i> coordinates of points.
	 * @param   nPoints  number of points.
	 */
	public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
		makePolygon(xPoints, yPoints, nPoints, NO_FILL);
	}

	/**
     	 * Fills a polygon, using the current color, bounded by points
         * <code>(x1,&nbsp;y1)</code> and <code>(x2,&nbsp;y2)</code> ... and <code>(x_n,&nbsp;y_n)</code>
	 * in this graphics context's coordinate system.
	 * @param   xPoints  the <i>x</i> coordinates of points.
      	 * @param   yPoints  the <i>y</i> coordinates of points.
	 * @param   nPoints  number of points.
	 */
	public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
		makePolygon(xPoints, yPoints, nPoints, FILL);
	}


	/** 
	 * Generates METAPOST code for the polygon  witch have to be filled or drawn.
	 * @param   xPoints  the <i>x</i> coordinates of points.
      	 * @param   yPoints  the <i>y</i> coordinates of points.
	 * @param   nPoints  number of points.
	 */
	public void makePolygon(int xPoints[], int yPoints[], int nPoints, int fill) {
		
		String command = null;
	
		switch(fill) {
			case 0: command = "fill"; break;
			case 1: command = "draw"; break;
			case 2: command = "clip"; break;
		}
		
		if(nPoints > 0) {

			String path ="";
			path += synGen.pair(xPoints[0], yPoints[0]);
			for (int i=1; i < nPoints; i++) {
				path += "--" + synGen.pair(xPoints[i], yPoints[i]);
			}
			path += "--cycle";
		
			stream.println( synGen.declaration( "path", "polygon" ) ); 
			stream.println( synGen.assignment( "polygon", path ) ); 
			stream.println( synGen.commandLine(command, "polygon" + " "
					+ "transformed t" + " " + "transformed default" + " "
					+ ((MPGraphicContext) this.gc).drawOptionsDescriptor())
				      );
		}
	}
	
	/**
      	 * Draws the text given by the specified string, using this
         * graphics context's current font and color. The baseline of the
	 * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in this
	 * graphics context's coordinate system.
	 * @param       str      the string to be drawn.
	 * @param       x        the <i>x</i> coordinate.
	 * @param       y        the <i>y</i> coordinate.
	 */		    
	public void drawString(String s, float x, float y) {
		stream.println( synGen.commandLine("draw", "\"" + s + "\"" + " " 
					+ "infont defaultfont scaled defaultscale" + " "
					+ synGen.shift(synGen.pair(x, y)) + " " 
					+ synGen.reflect(synGen.pair(0, y), synGen.pair(this.dim.getWidth(), y)) + " "
					+ "transformed t" + " " + "transformed default" + " " 
					+ ((MPGraphicContext) this.gc).drawOptionsDescriptor()) 
					);
	}

//---------------------------------------------------
//		Unimplemented methods
//---------------------------------------------------

	public void fill(Shape s) {
		// No-op 
	}
	
	public GraphicsConfiguration getDeviceConfiguration() {
		// No-op 
		return null;
	}
	
	public FontMetrics getFontMetrics(Font f) {
		// No-op 
		return fmg.getFontMetrics(f);
	}

	
	public void drawString(AttributedCharacterIterator iterator, float x, float y) {
		// No-op 
	}

	
	public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
		// No-op 
	}

	public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
		// No-op 
	}
	
	public void draw(Shape s) {
		// No-op 
	}

	public void dispose() {
		// No-op 
	}

	public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
		// No-op 
		return true;
	}

	public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
		// No-op 
		return true;
	}

	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
		// No-op 
	}

	public void setXORMode(Color c1) {
		// No-op 
	}

}
